/*
 * Copyright (2021) The Delta Lake Project Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.delta.suitegenerator

import java.nio.charset.StandardCharsets
import java.nio.file.{Files, Path}

import scala.collection.mutable.ListBuffer
import scala.io.Source
import scala.jdk.CollectionConverters._
import scala.meta._

import org.scalafmt.Scalafmt

/**
 * Contains the constants for the SuitesWriter class
 */
object SuitesWriter {
  private val LEGAL_HEADER =
    """ Copyright (2021) The Delta Lake Project Authors.
      |
      | Licensed under the Apache License, Version 2.0 (the "License");
      | you may not use this file except in compliance with the License.
      | You may obtain a copy of the License at
      |
      | http://www.apache.org/licenses/LICENSE-2.0
      |
      | Unless required by applicable law or agreed to in writing, software
      | distributed under the License is distributed on an "AS IS" BASIS,
      | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      | See the License for the specific language governing permissions and
      | limitations under the License.""".stripMargin

  private val WARNING_HEADER =
    """ ***********************************************************************************
      | * This file is automatically generated. Manual modification is not allowed.       *
      | * There is a unit test that should prevent merging a manual change.               *
      | *                                                                                 *
      | * To make changes to the suites, modify the generator script config at            *
      | * SuiteGeneratorConfig.scala and run it. The generator can be run via the         *
      | * sbt command deltaSuiteGenerator / run.                                          *
      | *                                                                                 *
      | * DO NOT TOUCH ANYTHING IN THIS FILE!                                             *
      | ***********************************************************************************"""
      .stripMargin

  private lazy val PACKAGE_NAME =
    ModularSuiteGenerator.GENERATED_PACKAGE.parse[Term].get.asInstanceOf[Term.Ref]

  private lazy val SRC_HEADERS =
    s"""/*
       |${LEGAL_HEADER.linesWithSeparators.map(" *" + _).mkString}
       | */
       |
       |${WARNING_HEADER.linesWithSeparators.map("//" + _).mkString}
       |
       |""".stripMargin

  private lazy val SCALAFMT_CONFIG = {
    val source = Source.fromURL(getClass.getClassLoader.getResource("scalafmt.conf"))
    try {
      Scalafmt.parseHoconConfig(source.mkString).get
    } finally {
      source.close()
    }
  }
}

/**
 * Used to write the generated suites. The output is written to the given directory. This directory
 * should not contain any manually written files, otherwise the generator will throw an error.
 * @param outputDir the path to the directory where the generated suites will be written.
 */
class SuitesWriter(val outputDir: Path) {

  import SuitesWriter._

  protected val allFiles: ListBuffer[Path] = ListBuffer.empty[Path]

  def writeGeneratedSuitesOfGroup(suites: List[TestSuite], testGroup: TestGroup): Unit = {
    suites
      // Group by parent class: first item of the extends clause (last item of class def)
      .groupBy(suite => suite.classDefinition.children.last.children.head.text)
      .foreach { case (baseSuite, suites) =>
        val src = SRC_HEADERS + "// scalastyle:off line.size.limit\n" +
          source"""package $PACKAGE_NAME
                   import ..${testGroup.imports}
                   ..${suites.sortBy(_.name).map(_.classDefinition)}"""
        val srcFile = outputDir.resolve(s"${testGroup.name}$baseSuite.scala")
        val formattedSrc = Scalafmt.format(src, SCALAFMT_CONFIG).get
        writeFile(srcFile, formattedSrc)
      }
    // scalastyle:off println
    println(s"Wrote ${suites.size} generated suites from ${testGroup.name} group.")
    // scalastyle:on println
  }

  protected def writeFile(file: Path, content: String): Unit = {
    Files.write(file, content.getBytes(StandardCharsets.UTF_8))
    allFiles += file
  }

  def conclude(): Unit = {
    val additionalFiles = Files.list(outputDir)
      .iterator()
      .asScala
      .filterNot(allFiles.contains)
      .map(_.getFileName)
      .mkString(", ")
    assert(additionalFiles.isEmpty, s"Unexpected files found in $outputDir: $additionalFiles. " +
      s"Please manually delete them.")
  }
}
